namespace ScrumSprintMonitor.ConfigEditor.Views.ConfigurationView
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.IO;
	using System.Linq;
	using System.Windows;

	using Implementation;
	using Implementation.Configuration;

	using Infrastructure;

	using Microsoft.Practices.Unity;

    #endregion

	internal class TeamConfigurationGenerator : IDisposable
	{
        private readonly IUnityContainer container;
		private FileDataStreamProvider fileStreamDataProvider;
		private StreamBasedSprintConfiguration sprintConfiguration;
		private WritableXmlStreamBasedTeamInfoProvider teamInfoConfiguration;
		private FileDataStreamProvider teamInfoFileStreamDataProvider;
		private IWorkItemStore workItemStore;

        public TeamConfigurationGenerator(IUnityContainer container)
		{
            if (container == null)
			{
                throw new ArgumentNullException("container");
			}
            this.container = container;
		}

		#region IDisposable Members

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		#endregion

		public event EventHandler Started;
		public event EventHandler Finished;

		private void OnStarted(EventArgs e)
		{
			var handler = Started;
			if (handler != null)
			{
				handler(this, e);
			}
		}

		private void OnFinished(EventArgs e)
		{
			var finished = Finished;
			if (finished != null)
			{
				finished(this, e);
			}
		}

		private void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this.fileStreamDataProvider != null)
				{
					this.fileStreamDataProvider.Dispose();
				}
				if (this.sprintConfiguration != null)
				{
					this.sprintConfiguration.Dispose();
				}
				if (this.teamInfoFileStreamDataProvider != null)
				{
					this.teamInfoFileStreamDataProvider.Dispose();
				}
				if (this.teamInfoConfiguration != null)
				{
					this.teamInfoConfiguration.Dispose();
				}

				if (this.workItemStore != null)
				{
					this.workItemStore.WorkItemsUpdated -= OnWorkItemsUpdated;
					this.workItemStore = null;
				}
			}
		}

		~TeamConfigurationGenerator()
		{
			Dispose(false);
		}

		public bool GenerateConfiguration(IConfigurationViewModel configurationViewModel)
		{
			if (File.Exists(configurationViewModel.TeamMembersConfigurationFilePath))
			{
				var result = MessageBox.Show("The target file already exists. Do you wish to overwrite it?", Application.Current.MainWindow.Title,
				                             MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.OK);
				if (result == MessageBoxResult.Cancel)
				{
					return false;
				}
			}

			OnStarted(EventArgs.Empty);

            var connection = this.container.Resolve<IConnectionProvider>().ProvideConnectionTo(configurationViewModel.TfsServerUri,
			                                                                            configurationViewModel.TfsServerUserName,
			                                                                            configurationViewModel.TfsServerPassword, true);
			if (!connection.IsConnected)
			{
				connection.Connect();
			}

			this.fileStreamDataProvider = new FileDataStreamProvider(configurationViewModel.ConfigurationFilePath,
                                                                     this.container.Resolve<ILogger<FileDataStreamProvider>>());
			this.sprintConfiguration = new StreamBasedSprintConfiguration(this.fileStreamDataProvider,
                                                                          this.container.Resolve<ILogger<StreamBasedSprintConfiguration>>());
			this.teamInfoFileStreamDataProvider = new FileDataStreamProvider(configurationViewModel.TeamMembersConfigurationFilePath,
                                                                             this.container.Resolve<ILogger<FileDataStreamProvider>>());
			this.teamInfoConfiguration = new WritableXmlStreamBasedTeamInfoProvider(this.teamInfoFileStreamDataProvider,
                                                                                    this.container.Resolve<ILogger<XmlStreamBasedTeamInfoProvider>>());

			this.workItemStore = connection.GetWorkItemStore(this.sprintConfiguration);
			this.workItemStore.WorkItemsUpdated += OnWorkItemsUpdated;
			this.workItemStore.Refresh();

			return true;
		}

		private void OnWorkItemsUpdated(object sender, EventArgs e)
		{
			var workItems = this.workItemStore.WorkItems.Where(w => w.Type == WorkItemType.SprintBacklogItem);
			var teamMemberNames = (from w in workItems where !string.IsNullOrEmpty(w.AssignedTo) select w.AssignedTo).Distinct();
			var teamMembersList = teamMemberNames.Select(m => new
			                                                  	{
			                                                  		Name = m,
			                                                  		Team = GetTeamMemberTeam(m, workItems)
			                                                  	});

			var workdayStart = new DateTime(1900, 1, 1, 9, 0, 0, 0);
			var workdayEnd = new DateTime(1900, 1, 1, 18, 0, 0, 0);
			var lunchHourStart = new DateTime(1900, 1, 1, 12, 0, 0, 0);
			var lunchHourEnd = new DateTime(1900, 1, 1, 13, 0, 0, 0);
			const double hourlyProductivityFactor = 0.75;

			var unassignedTeamMemberInfo = new TeamMemberInformation(null, true, null, null, null, string.Empty, TimeZoneInfo.Local, workdayStart, workdayEnd,
			                                                         lunchHourStart, lunchHourEnd, hourlyProductivityFactor, false);
			this.teamInfoConfiguration.AddTeamMember(unassignedTeamMemberInfo);

			string lastTeam = null;
			foreach (var teamMember in teamMembersList.OrderBy(t => t.Team))
			{
				Debug.WriteLine(string.Format("{0}, {1} team.", teamMember.Name, teamMember.Team));

				if (teamMember.Team != lastTeam)
				{
					lastTeam = teamMember.Team;

					if (!string.IsNullOrEmpty(teamMember.Team))
					{
						unassignedTeamMemberInfo = new TeamMemberInformation(null, true, null, null, null, teamMember.Team, TimeZoneInfo.Local, workdayStart, workdayEnd,
						                                                     lunchHourStart, lunchHourEnd, hourlyProductivityFactor, false);
						this.teamInfoConfiguration.AddTeamMember(unassignedTeamMemberInfo);
					}
				}

				this.teamInfoConfiguration.AddTeamMember(new TeamMemberInformation(teamMember.Name, false, teamMember.Name, teamMember.Name, null, teamMember.Team,
				                                                                   TimeZoneInfo.Local, workdayStart, workdayEnd, lunchHourStart, lunchHourEnd,
				                                                                   hourlyProductivityFactor, true));
			}

			this.teamInfoConfiguration.Save();

			OnFinished(EventArgs.Empty);
		}

		private static string GetTeamMemberTeam(string teamMemberName, IEnumerable<IWorkItemAdapter> workItems)
		{
			string name = teamMemberName;
			var teamMemberWorkItems = workItems.Where(w => w.AssignedTo == name);
			var groupedByTeam = teamMemberWorkItems.GroupBy(w => w.Team);

			string team;
			if (groupedByTeam.Count() == 1)
			{
				team = groupedByTeam.First().Key;
			}
			else
			{
				int maxInstancesOfTeamWorkItems = groupedByTeam.Max(g => g.Count());

				string mostUsedTeam = groupedByTeam.First(g => g.Count() == maxInstancesOfTeamWorkItems).Key;

				team = mostUsedTeam;
			}

			return team;
		}
	}
}